﻿/***************************************************************************
 * 
 *  Copyright 2008 Anthony Tambrin
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 *  
 *  @page 				PreviewScrollBar.as
 *  @version			0.1
 *  @description		Interactive scrollbar component class. 
 * 						Features include:
 * 						1) Scroll animation
 * 						2) Thumbnail preview
 * 
 ***************************************************************************/

package com.ui {
	
	/********************************************************
	 * Imports
	 ********************************************************/
	import flash.display.MovieClip;
	import flash.events.MouseEvent;
	import flash.events.Event;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.geom.Matrix;
	import flash.display.Stage;
	
	public class PreviewScrollBar extends MovieClip {
		
		/********************************************************
		 * Private Constants
		 ********************************************************/
		private static const FRAME_LABEL_UP:String = "up";
		private static const FRAME_LABEL_OVER:String = "over";
		private static const DEFAULT_INSTANCE_THUMB_BUTTON:String = "thumbButton";
		private static const DEFAULT_INSTANCE_THUMB_TRACK:String = "thumbTrack";
		private static const DEFAULT_INSTANCE_UP_BUTTON:String = "upButton";
		private static const DEFAULT_INSTANCE_DOWN_BUTTON:String = "downButton";
		private static const DEFAULT_THUMB_MOVEMENT_DELAY:Number = 0.2;
		private static const DEFAULT_PREVIEW_ALPHA_DELAY:Number = 0.2;
		private static const SNAP_TOLERANCE:Number = 0.1;
		
		/********************************************************
		 * Private Components
		 ********************************************************/
		private var _stage:Stage;
		
		private var _container:MovieClip;
		private var _thumbButton:MovieClip; 
		private var _thumbTrack:MovieClip;
		private var _content:MovieClip; 
		private var _mask:MovieClip;
		private var _upButton:MovieClip;
		private var _downButton:MovieClip;
		
		private var _preview:MovieClip;
		private var _previewDummy:MovieClip;
		private var _previewHighlight:MovieClip;
		private var _previewMask:MovieClip;
		private var _previewOutline:MovieClip;
		
		private var _thumbButtonInstanceName:String;
		private var _thumbTrackInstanceName:String;
		private var _upButtonInstanceName:String;
		private var _downButtonInstanceName:String;
		
		/********************************************************
		 * Private Members
		 ********************************************************/
		private var _dy:Number;
		private var _contentY0:Number;
		private var _scrollStep:Number;
		private var _topBoundary:Number;
		private var _bottomBoundary:Number;
		private var _maxThumbHeight:Number;
		private var _thumbButtonRatio:Number;
		private var _contentRatio:Number;
		private var _trueTrackHeight:Number;
		private var _trueContentMovement:Number;
		private var _isFixedThumbHeight:Boolean;
		private var _isThumbPressed:Boolean;
		private var _isRollOver:Boolean;
		private var _targetThumbY:Number;
		private var _thumbMovementDelay:Number;
		private var _tempN:Number;
		private var _moveRatio:Number;
		private var _targetPreviewAlpha:Number;
		private var _previewAlphaDelay:Number;
	
		/********************************************************
		 * Constructor
		 ********************************************************/
		public function PreviewScrollBar(p_scrollbar:MovieClip, p_content:MovieClip, p_mask:MovieClip, p_initObject:Object=null) {
			p_initObject = p_initObject ? p_initObject : new Object();
	  		_isFixedThumbHeight = p_initObject.isFixedThumbHeight ? p_initObject.isFixedThumbHeight : true;
	  		_thumbButtonInstanceName = p_initObject.thumbButtonInstanceName ? p_initObject.thumbButtonInstanceName : DEFAULT_INSTANCE_THUMB_BUTTON;
	  		_thumbTrackInstanceName = p_initObject.thumbTrackInstanceName ? p_initObject.thumbTrackInstanceName : DEFAULT_INSTANCE_THUMB_TRACK;
	  		_upButtonInstanceName = p_initObject.upButtonInstanceName ? p_initObject.upButtonInstanceName : DEFAULT_INSTANCE_UP_BUTTON;
	  		_downButtonInstanceName = p_initObject.downButtonInstanceName ? p_initObject.downButtonInstanceName : DEFAULT_INSTANCE_DOWN_BUTTON;
	  
	  		_container = p_scrollbar;
			_content = p_content;
			_mask = p_mask;
			_contentY0 = _content.y;
			
			_container.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			
			if (_container.stage) {
				mainStage = _container.stage;
			}
			
			initComponents();
			initMembers();
			initPreview();
			
			_container.addEventListener(Event.ENTER_FRAME, onContainerEnterFrame);
		}
	
		/********************************************************
		 * Initialisation Methods
		 ********************************************************/
		private function initMembers():void {
			_scrollStep = 20;
			_isFixedThumbHeight = true;
			_isThumbPressed = false;
			_isRollOver = false;
			_dy = 0;
			_thumbMovementDelay = DEFAULT_THUMB_MOVEMENT_DELAY;
			_previewAlphaDelay = DEFAULT_PREVIEW_ALPHA_DELAY;
			_targetPreviewAlpha = 0;
			moveThumb(0);
		}
		
		private function initComponents():void {
			_thumbButton = _container[_thumbButtonInstanceName];
			_thumbTrack = _container[_thumbTrackInstanceName];
			_upButton = _container[_upButtonInstanceName];
			_downButton = _container[_downButtonInstanceName];
			
			if (!_thumbButton) throw new Error("Thumb button [" + _thumbButtonInstanceName + "] cannot be found.");
			if (!_thumbTrack) throw new Error("Thumb track [" + _thumbTrackInstanceName + "] cannot be found.");
			if (!_upButton) throw new Error("Up arrow button [" + _upButtonInstanceName + "] cannot be found.");
			if (!_downButton) throw new Error("Down arrow button [" + _downButtonInstanceName + "] cannot be found.");
			
			_maxThumbHeight = _thumbTrack.height;
			
			initThumb();
			initBoundaries();
			initTrueHeights();
			initThumbTrack();
			initArrowButtons();
		}
		
		private function initTrueHeights():void {
			_trueTrackHeight = _thumbTrack.height - _thumbButton.height;
			_trueContentMovement = (_contentY0 + _content.height) - (_mask.y + _mask.height);
		}
		
		private function initBoundaries():void {
			_topBoundary = _thumbTrack.y;
			_bottomBoundary = _topBoundary + _maxThumbHeight;
		}
		
		private function initThumbTrack():void {
			_thumbTrack.addEventListener(MouseEvent.MOUSE_DOWN, onThumbTrackPress);
			_thumbTrack.addEventListener(MouseEvent.MOUSE_OVER, onContainerMouseOver);
			_thumbTrack.addEventListener(MouseEvent.MOUSE_OUT, onContainerMouseOut);
		}
		
		private function initThumb():void {
			resizeThumb();
			_thumbButton.addEventListener(MouseEvent.MOUSE_DOWN, onThumbButtonPress);
			_thumbButton.addEventListener(MouseEvent.MOUSE_UP, onThumbButtonRelease);
			_thumbButton.addEventListener(MouseEvent.MOUSE_OVER, onContainerMouseOver);
			_thumbButton.addEventListener(MouseEvent.MOUSE_OUT, onContainerMouseOut);
		}
		
		private function initArrowButtons():void {
			_downButton.addEventListener(MouseEvent.MOUSE_DOWN, onDownButtonPress);
			_downButton.addEventListener(MouseEvent.MOUSE_UP, onDownButtonRelease);
			_upButton.addEventListener(MouseEvent.MOUSE_DOWN, onUpButtonPress);
			_upButton.addEventListener(MouseEvent.MOUSE_UP, onUpButtonRelease);
			_downButton.addEventListener(MouseEvent.MOUSE_OVER, onContainerMouseOver);
			_downButton.addEventListener(MouseEvent.MOUSE_OUT, onContainerMouseOut);
			_upButton.addEventListener(MouseEvent.MOUSE_OVER, onContainerMouseOver);
			_upButton.addEventListener(MouseEvent.MOUSE_OUT, onContainerMouseOut);
		}
		
		private function initPreview():void {
			// init the preview thumbnail
			_preview = new MovieClip();
			var matrix:Matrix = new Matrix();
			var scale:Number = _thumbTrack.height / _content.height;
			matrix.scale(scale, scale);
			var previewBitmapData:BitmapData = new BitmapData(_content.width*scale, _content.height*scale, true, 0x000000);
			previewBitmapData.draw(_content, matrix, null, null, null, true);
			var previewBitmap:Bitmap = new Bitmap(previewBitmapData);
			
			// init the preview textual background
			var previewTextBG:MovieClip = new MovieClip();
			previewTextBG.addChild(previewBitmap);
			previewTextBG.alpha = 0.6;
			
			// init the preview highlight text (dummy)
			_previewDummy = new MovieClip();
			_previewDummy.addChild(new Bitmap(previewBitmap.bitmapData.clone()));
			
			_preview.x = _thumbTrack.x - previewBitmap.width - 20;
			_preview.y = _thumbTrack.y;
			
			// init the preview background
			var previewBG:MovieClip = new MovieClip();
			with (previewBG.graphics) {
				beginFill(0x333333, 0.8);
				drawRoundRect(0, 0, _previewDummy.width+20, _previewDummy.height+20, 10, 10);
				endFill();
			}
			previewBG.x = -10;
			previewBG.y = -10;
			
			_preview.addChild(previewBG);
			_preview.addChild(previewTextBG);
			_preview.addChild(_previewDummy);
			
			_container.addChild(_preview);
			
			// init the preview highlight
			_previewHighlight = new MovieClip();
			
			// init the mask
			_previewMask = new MovieClip();
			var maskHeightRatio:Number = _mask.height / _mask.width;
			with (_previewMask.graphics) {
				beginFill(0x0000FF, 1);
				drawRect(0, 0, previewBitmap.width, previewBitmap.width*maskHeightRatio);
				endFill();
			}
			_previewDummy.mask = _previewMask;
			_previewHighlight.addChild(_previewMask);
			
			// init the outline
			_previewOutline = new MovieClip();
			with (_previewOutline.graphics) {
				lineStyle(1, 0xFF9C00, 0.5);
				drawRect(0, 0, _previewMask.width, _previewMask.height);
			}
			_previewHighlight.addChild(_previewOutline);
			
			_previewHighlight.x = _preview.x;
			_previewHighlight.y = _preview.y;
			_container.addChild(_previewHighlight);
		}
		
		private function initStageListeners():void {
			if (!_stage) return;
		
			_stage.removeEventListener(MouseEvent.MOUSE_UP, onComponentReleaseOutside);
			_stage.removeEventListener(MouseEvent.MOUSE_MOVE, onComponentMoveOutside);
			_stage.removeEventListener(MouseEvent.MOUSE_WHEEL, onComponentWheelOutside);
			_stage.addEventListener(MouseEvent.MOUSE_UP, onComponentReleaseOutside);
			_stage.addEventListener(MouseEvent.MOUSE_MOVE, onComponentMoveOutside);
			_stage.addEventListener(MouseEvent.MOUSE_WHEEL, onComponentWheelOutside);
		}
		
		/********************************************************
		 * Accessors
		 ********************************************************/
		public function set scrollStep(p_scrollStep:Number):void {
			_scrollStep = p_scrollStep;
		}
		
		public function get isScrollRequired():Boolean {
			if (_content.height > _mask.height) return true;
			return false;	
		}
		
		public function set delay(p_thumbMovementDelay:Number):void {
			_thumbMovementDelay = p_thumbMovementDelay;
		}
		public function get delay():Number {
			return _thumbMovementDelay;
		}
		
		public function set mainStage(p_stage:Stage):void {
			_stage = p_stage;
			initStageListeners();
		}
		
		/********************************************************
		 * Private Methods
		 ********************************************************/	
		private function resetScroll():void {
			_thumbButton.visible = true;
			resizeThumb();
			initTrueHeights();
			initThumbTrack();
			
			if (_thumbButton.y + _thumbButton.height > _thumbTrack.y + _thumbTrack.height) {
				_thumbButton.y = _trueTrackHeight;
			}
		}
		
		private function resizeThumb():void {
			if (!_isFixedThumbHeight) {
				_thumbButtonRatio = _maxThumbHeight / _content.height;
				_thumbButton.height = _thumbButtonRatio*_maxThumbHeight;
			}
		}
		
		private function moveThumb(p_y:Number):void {
			if (p_y < _topBoundary) {
				p_y = _topBoundary;
			} else if (p_y + _thumbButton.height > _bottomBoundary) {
				p_y = _bottomBoundary - _thumbButton.height;
			}
			_targetThumbY = p_y;
		}
		
		/********************************************************
		 * Event Handlers
		 ********************************************************/
		private function onAddedToStage(p_event:Event):void {
			if (_container.stage) mainStage = _container.stage;
		} 
		
		private function onContainerEnterFrame(p_event:Event):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			// update thumb position
			if (_targetThumbY != _thumbButton.y) {
				_tempN = (_targetThumbY - _thumbButton.y) * _thumbMovementDelay;
				_thumbButton.y += _tempN;
				_moveRatio = (_thumbButton.y - _topBoundary) / _trueTrackHeight;
				_content.y = _contentY0 - (_moveRatio * _trueContentMovement);
				_previewHighlight.y = ((_contentY0 - _content.y) / _content.height) * _previewDummy.height + _preview.y;
				if (_tempN >= -SNAP_TOLERANCE && _tempN <= SNAP_TOLERANCE) {
					_thumbButton.y = _targetThumbY;
				}
			}
			// update preview alpha
			if (_targetPreviewAlpha != _preview.alpha) {
				_tempN = (_targetPreviewAlpha - _preview.alpha) * _previewAlphaDelay;
				_preview.alpha += _tempN;
				if (_tempN >= -SNAP_TOLERANCE && _tempN <= SNAP_TOLERANCE) {
					_preview.alpha = _targetPreviewAlpha;
				}
				_previewHighlight.alpha = _preview.alpha;
			}
		}
		
		private function onContainerMouseOver(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isRollOver = true;
			_targetPreviewAlpha = 1;
		}
		
		private function onContainerMouseOut(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isRollOver = false;
			if (!_isThumbPressed) {
				_targetPreviewAlpha = 0;
			}
		}
		 
		private function onDownButtonPress(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = true;
			moveThumb(_targetThumbY += _scrollStep);
		}
		
		private function onDownButtonRelease(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = false;
		}
		
		private function onUpButtonPress(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = true;
			moveThumb(_targetThumbY -= _scrollStep);
		}
		
		private function onUpButtonRelease(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = false;
			if (!_isRollOver) {
				_targetPreviewAlpha = 0;
			}
		}
		
		private function onThumbTrackPress(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			moveThumb(p_event.stageY - (_thumbButton.height / 2) - _container.y);
		}
		
		private function onThumbButtonPress(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = true;
			_dy = p_event.stageY - _thumbButton.y;
		}
		
		private function onThumbButtonRelease(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = false;
		}
		
		private function onComponentReleaseOutside(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			_isThumbPressed = false;
			if (!_isRollOver) {
				_targetPreviewAlpha = 0;
			}
		}
		
		private function onComponentMoveOutside(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			if (!_isThumbPressed) return;
			moveThumb(p_event.stageY - _dy);
		}
		
		private function onComponentWheelOutside(p_event:MouseEvent):void {
			// ignore if this component is disabled
			if (!enabled) return;
			
			moveThumb(_targetThumbY - p_event.delta);
		}
	}
}